iBetter Books
수정

PART 04의 마무리는 임베딩을 눈으로 보는 실습입니다. 512차원은 그릴 수 없지만, t-SNE라는 도구로 2차원에 펼치면 "같은 사람끼리 모인다"는 1장의 직관이 점들의 군집으로 나타납니다. 여러 사람의 얼굴을 모아 임베딩을 뽑고, 한 장의 그림으로 확인해 봅니다.

준비 — 작은 얼굴 데이터셋

이 실습에는 여러 사람의 얼굴 사진이 필요합니다. 인물별로 폴더를 나눠 몇 장씩 담아 둡니다.

faces/
├── 홍길동/   img1.jpg  img2.jpg  img3.jpg
├── 김철수/   img1.jpg  img2.jpg
└── 이영희/   img1.jpg  img2.jpg  img3.jpg

사람마다 3장 안팎, 사람은 3~5명만 있어도 군집을 보기에 충분합니다. 본인과 가족, 또는 공개된 인물 사진을 써도 됩니다.

t-SNE란

t-SNE(t-distributed Stochastic Neighbor Embedding)는 고차원 데이터를 2~3차원으로 줄여 보여 주는 시각화 도구입니다. 핵심 성질은 "가까운 것은 가깝게, 먼 것은 멀게"를 최대한 보존한다는 점입니다. 그래서 512차원 임베딩을 t-SNE로 2차원에 펼치면, 원래 가까웠던 같은 사람의 임베딩들이 그림에서도 한 군데에 모입니다.

주의할 점은 t-SNE의 축에는 의미가 없다는 것입니다. 가로·세로 값 자체는 해석하지 않고, 오직 "어떤 점들이 뭉쳐 있는가"라는 군집 구조만 봅니다.

전체 코드

scikit-learn의 t-SNE와 matplotlib이 필요합니다(pip install scikit-learn matplotlib). 다음 내용을 tsne_view.py로 저장합니다.

# 파일: tsne_view.py"""여러 사람의 얼굴 임베딩을 t-SNE로 2D에 펼쳐 군집을 본다."""import osimport globimport torchimport numpy as npimport matplotlib.pyplot as pltfrom PIL import Imagefrom facenet_pytorch import MTCNN, InceptionResnetV1from sklearn.manifold import TSNEmtcnn = MTCNN(image_size=160, margin=0)resnet = InceptionResnetV1(pretrained="vggface2").eval()DATA_DIR = "faces"embeddings, labels = [], []for person in sorted(os.listdir(DATA_DIR)):    person_dir = os.path.join(DATA_DIR, person)    if not os.path.isdir(person_dir):        continue    for path in glob.glob(os.path.join(person_dir, "*.jpg")):        img = Image.open(path).convert("RGB")        face = mtcnn(img)        if face is None:            continue        with torch.no_grad():            emb = resnet(face.unsqueeze(0))[0].numpy()        embeddings.append(emb)        labels.append(person)X = np.array(embeddings)print(f"임베딩 {len(X)}개 수집")# 512차원 → 2차원. perplexity는 표본 수보다 작게 잡는다tsne = TSNE(n_components=2, perplexity=min(5, len(X) - 1), random_state=42)coords = tsne.fit_transform(X)# 사람마다 다른 색으로 점 찍기plt.figure(figsize=(8, 6))for person in sorted(set(labels)):    idx = [i for i, l in enumerate(labels) if l == person]    plt.scatter(coords[idx, 0], coords[idx, 1], label=person, s=60)plt.legend()plt.title("Face embeddings (t-SNE)")plt.tight_layout()plt.savefig("tsne_result.png", dpi=120)print("저장 완료: tsne_result.png")

코드는 세 단계입니다. 먼저 인물 폴더를 돌며 얼굴마다 512차원 임베딩을 모으고, 다음으로 t-SNE로 2차원 좌표를 구하고, 마지막으로 사람별 색으로 점을 찍습니다. perplexity는 t-SNE가 이웃을 몇 개나 고려할지를 정하는 값으로, 표본이 적을 때는 작게(표본 수보다 작게) 잡아야 오류가 나지 않습니다.

무엇을 보게 되나

실행하면 같은 사람의 점들이 같은 색으로 뭉쳐 있고, 다른 사람은 떨어진 다른 군집을 이루는 그림이 나옵니다. 이것이 1장에서 말로만 설명한 "임베딩 공간에서 같은 사람끼리 모인다"의 실제 모습입니다. 만약 어떤 사람의 점들이 흩어져 있다면, 그 사진들의 화질·각도·조명이 들쭉날쭉하거나 정렬이 잘 안 됐을 가능성을 의심할 수 있습니다. t-SNE 그림은 데이터 품질을 점검하는 도구로도 유용합니다.

실무 팁. t-SNE 결과는 실행할 때마다 모양이 조금씩 달라지고, perplexity 값에 민감합니다. 그래서 t-SNE는 "정확한 좌표"가 아니라 "군집이 잘 나뉘는가"라는 큰 경향만 읽는 용도로 쓰세요. 군집이 또렷하게 갈린다면 임베딩이 사람을 잘 구분하고 있다는 좋은 신호입니다. 좌표 자체에 의미를 부여하거나, 한 번의 그림으로 결론을 단정하지 않는 것이 안전합니다.

이 장에서 기억할 것

t-SNE는 512차원 임베딩을 2차원에 펼쳐, 같은 사람끼리 군집을 이루는 모습을 눈으로 보여 줍니다. 인물별 폴더에서 임베딩을 모아 TSNE로 줄이고 색으로 구분해 찍으면 됩니다. 축에는 의미가 없고 군집 구조만 보며, 데이터 품질 점검에도 쓸 수 있습니다. 이로써 PART 04의 직관이 완성됐습니다. 다음 PART 05부터는 이 원리를 실제 라이브러리로 구현합니다. 가장 쉬운 입문인 face_recognition부터 시작합니다.